/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.vysper.storage.jcr.roster;
import java.util.ArrayList;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import org.apache.vysper.storage.jcr.JcrStorage;
import org.apache.vysper.storage.jcr.JcrStorageException;
import org.apache.vysper.xmpp.addressing.Entity;
import org.apache.vysper.xmpp.addressing.EntityFormatException;
import org.apache.vysper.xmpp.addressing.EntityImpl;
import org.apache.vysper.xmpp.modules.roster.AskSubscriptionType;
import org.apache.vysper.xmpp.modules.roster.MutableRoster;
import org.apache.vysper.xmpp.modules.roster.Roster;
import org.apache.vysper.xmpp.modules.roster.RosterException;
import org.apache.vysper.xmpp.modules.roster.RosterGroup;
import org.apache.vysper.xmpp.modules.roster.RosterItem;
import org.apache.vysper.xmpp.modules.roster.SubscriptionType;
import org.apache.vysper.xmpp.modules.roster.persistence.AbstractRosterManager;
import org.apache.vysper.xmpp.protocol.NamespaceURIs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* roster items are stored for contacts in the following path:
* /accountentity/user@vysper.org/jabber_iq_roster/contact@vysper.org
* all item properties besides contact jid (which is used as a node name)
* are stored as node properties
*
* @author The Apache MINA Project (dev@mina.apache.org)
*/
public class JcrRosterManager extends AbstractRosterManager {
final Logger logger = LoggerFactory.getLogger(JcrRosterManager.class);
protected JcrStorage jcrStorage;
public JcrRosterManager(JcrStorage jcrStorage) {
this.jcrStorage = jcrStorage;
}
/*package*/static Node retrieveRosterNode(JcrStorage jcrStorage, Entity bareJid) {
try {
if (jcrStorage.getEntityNode(bareJid, null, false) == null)
return null;
return jcrStorage.getEntityNode(bareJid, NamespaceURIs.JABBER_IQ_ROSTER, true);
} catch (JcrStorageException e) {
return null;
}
}
@Override
protected Roster retrieveRosterInternal(Entity bareJid) {
final Node rosterNode = retrieveRosterNode(jcrStorage, bareJid);
MutableRoster roster = new MutableRoster();
NodeIterator nodes = null;
try {
nodes = rosterNode.getNodes();
} catch (RepositoryException e) {
return roster; // empty roster object
}
while (nodes != null && nodes.hasNext()) {
Node node = nodes.nextNode();
String contactJidString = null;
try {
contactJidString = node.getName();
} catch (RepositoryException e) {
logger.warn("when loading roster for user {} cannot read node name for node id = " + node.toString());
}
logger.warn("try now loading contact " + contactJidString + " from node " + node.toString());
EntityImpl contactJid = null;
if (contactJidString != null) {
try {
contactJid = EntityImpl.parse(contactJidString);
} catch (EntityFormatException e) {
logger.warn("when loading roster for user {} parsing contact jid {}", bareJid, contactJidString);
}
}
if (contactJid == null) {
logger.warn("when loading roster for user {}, skipping a contact due to missing or unparsable jid",
bareJid);
continue;
}
String name = readAttribute(node, "name");
String typeString = readAttribute(node, "type");
SubscriptionType subscriptionType = null;
try {
subscriptionType = SubscriptionType.valueOf(typeString == null ? "NONE" : typeString.toUpperCase());
} catch (IllegalArgumentException e) {
logger.warn("when loading roster for user " + bareJid + ", contact " + contactJid
+ " misses a subscription type", bareJid, contactJid);
}
String askTypeString = readAttribute(node, "askType");
AskSubscriptionType askSubscriptionType = AskSubscriptionType.NOT_SET;
try {
if (askTypeString != null)
askSubscriptionType = AskSubscriptionType.valueOf(askTypeString);
} catch (IllegalArgumentException e) {
logger.warn("when loading roster for user " + bareJid.getFullQualifiedName() + ", contact "
+ contactJid.getFullQualifiedName() + ", the ask subscription type is unparsable. skipping!");
continue; // don't return it, don't set a default!
}
List<RosterGroup> groups = new ArrayList<RosterGroup>();
// TODO read groups
RosterItem item = new RosterItem(contactJid, name, subscriptionType, askSubscriptionType, groups);
logger.info("item loaded for " + bareJid.getFullQualifiedName() + ": " + item.toString());
roster.addItem(item);
}
return roster;
}
private String readAttribute(Node node, String propertyName) {
try {
Property property = node.getProperty(propertyName);
if (property == null)
return null;
return property.getString();
} catch (RepositoryException e) {
return null;
}
}
@Override
protected Roster addNewRosterInternal(Entity jid) {
return new MutableRoster();
}
@Override
public void addContact(Entity jid, RosterItem rosterItem) throws RosterException {
if (jid == null)
throw new RosterException("jid not provided");
if (rosterItem.getJid() == null)
throw new RosterException("contact jid not provided");
// TODO think about concurrent updates
Entity contactJid = rosterItem.getJid().getBareJID();
Node contactNode = getOrCreateContactNode(jid, contactJid);
try {
setOrRemoveAttribute(contactNode, "name", rosterItem.getName());
String subscriptionTypeValue = rosterItem.getSubscriptionType() == null ? null : rosterItem
.getSubscriptionType().value();
setOrRemoveAttribute(contactNode, "type", subscriptionTypeValue);
String askSubscriptionTypeValue = null;
if (rosterItem.getAskSubscriptionType() != null
&& rosterItem.getAskSubscriptionType() != AskSubscriptionType.NOT_SET) {
askSubscriptionTypeValue = rosterItem.getAskSubscriptionType().value();
}
setOrRemoveAttribute(contactNode, "askType", askSubscriptionTypeValue);
contactNode.save();
logger.info("JCR node created/updated: " + contactNode);
} catch (RepositoryException e) {
throw new RosterException("failed to add contact node to roster for user = " + jid.getFullQualifiedName()
+ " and contact jid = " + rosterItem.getJid().getFullQualifiedName(), e);
}
}
private void setOrRemoveAttribute(Node contactNode, String attributeName, String attributeValue)
throws RepositoryException {
if (attributeValue != null)
contactNode.setProperty(attributeName, attributeValue);
else if (contactNode.hasProperty(attributeName))
contactNode.setProperty(attributeName, (String) null);
}
private Node getOrCreateContactNode(Entity jid, Entity contactJid) throws RosterException {
Node entityNode = null;
try {
entityNode = jcrStorage.getEntityNode(jid, NamespaceURIs.JABBER_IQ_ROSTER, true);
} catch (JcrStorageException e) {
throw new RosterException("failed to create roster store for " + jid.getFullQualifiedName(), e);
}
Node contactNode = null;
try {
contactNode = entityNode.getNode(contactJid.getFullQualifiedName());
} catch (RepositoryException e) {
// not exists, create
try {
contactNode = entityNode.addNode(contactJid.getFullQualifiedName());
entityNode.save();
} catch (RepositoryException addNodeEx) {
throw new RosterException("failed to add contact node to roster for user = "
+ jid.getFullQualifiedName() + " and contact jid = " + contactJid.getFullQualifiedName(),
addNodeEx);
}
}
return contactNode;
}
@Override
public void removeContact(Entity jidUser, Entity jidContact) throws RosterException {
if (jidUser == null)
throw new RosterException("jid not provided");
if (jidContact == null)
throw new RosterException("contact jid not provided");
Node rosterNode = null;
try {
rosterNode = jcrStorage.getEntityNode(jidUser, NamespaceURIs.JABBER_IQ_ROSTER, false);
} catch (JcrStorageException e) {
throw new RosterException("failed to retrieve roster store for " + jidUser.getFullQualifiedName(), e);
}
if (rosterNode == null)
return; // done, no contacts anyway. oops
NodeIterator nodes = null;
try {
nodes = rosterNode.getNodes("contact");
} catch (RepositoryException e) {
return; // failed to find any contacts, done.
}
boolean foundOne = false;
while (nodes != null && nodes.hasNext()) {
Node node = nodes.nextNode();
String contactJidString = readAttribute(node, "jid");
if (contactJidString != null && contactJidString.equals(jidContact.getFullQualifiedName())) {
foundOne = true;
try {
node.remove();
} catch (RepositoryException e) {
logger.warn("failed to remove from roster for user {} the contact jid " + jidContact, jidUser, e);
}
}
}
if (!foundOne)
logger.warn("failed to remove from roster for user " + jidUser + " the contact jid " + jidContact);
}
}